Skip to content

feat: enable Claude in Chrome MCP with full browser control#93

Merged
claude-code-best merged 1 commit intoclaude-code-best:mainfrom
amDosion:feat/enable-chrome-mcp
Apr 3, 2026
Merged

feat: enable Claude in Chrome MCP with full browser control#93
claude-code-best merged 1 commit intoclaude-code-best:mainfrom
amDosion:feat/enable-chrome-mcp

Conversation

@amDosion
Copy link
Copy Markdown
Contributor

@amDosion amDosion commented Apr 3, 2026

Summary

  • 替换 @ant/claude-for-chrome-mcp 的 6 行 stub 为完整实现(8 文件,3038 行)
  • 提供 17 个浏览器工具:navigate、screenshot、click、type、read DOM、execute JS、record GIF、monitor console/network 等
  • DEV-LOG.md 追加章节
  • docs/features/claude-in-chrome-mcp.md 完整计划与测试文档

Why

/chrome 命令已可见但功能为空壳——createClaudeForChromeMcpServer() 返回 nullBROWSER_TOOLS 为空数组。

src/ 下所有 claudeInChrome 源码已与官方一致(0 行差异),只需替换 stub 包。

不需要 feature flag,不改 dev.ts/build.ts,不改 src/ 下任何文件。

Test plan

  • bun run build 成功,产物包含 javascript_toolListToolsRequestSchema
  • bun run dev -- --chrome/chrome 显示设置菜单
  • 不带 --chrome 启动正常,无 chrome 报错
  • 现有 /voice/schedule 不受影响

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Enabled Claude in Chrome MCP for browser automation: Added comprehensive browser control capabilities allowing Claude Code CLI to interact with Chrome through the Model Context Protocol. Supports local socket and WebSocket bridge connections with 17+ tools including JavaScript execution, navigation, element interaction, form input, screenshots, GIF recording, tab management, and browser device switching.

Replace the 6-line stub in @ant/claude-for-chrome-mcp with the complete
implementation (8 files, 3038 lines) from the reference project.

Provides 17 browser tools: navigate, screenshot, click, type, read DOM,
execute JS, record GIF, monitor console/network, manage tabs, etc.

No feature flag needed. No changes to src/ (already matches official).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Implements a complete Chrome MCP (Model Context Protocol) integration, replacing stub implementations with full-featured bridge WebSocket client, multi-path socket clients, socket pool management, 17 browser tool definitions, request routing, permission handling, and supporting type definitions. Total ~3,389 lines of implementation code plus documentation.

Changes

Cohort / File(s) Summary
Type Definitions & Configuration
packages/@ant/claude-for-chrome-mcp/src/types.ts
Defines shared type contracts: Logger, PermissionMode, BridgeConfig, ChromeExtensionInfo, ClaudeForChromeContext, SocketClient interface, permission flow types, and localPlatformLabel() utility.
Bridge WebSocket Transport
packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts
Implements BridgeClient with WebSocket connection lifecycle, lazy extension discovery, pairing request/response handling, multi-extension routing, browser switching, permission prompting, tool-call tracking with timeout, and reconnection backoff logic.
Local Socket Transports
packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts, packages/@ant/claude-for-chrome-mcp/src/mcpSocketPool.ts
Adds McpSocketClient for single Unix/Windows socket with length-prefixed framing, security validation, and auto-reconnect; adds McpSocketPool for managing multiple clients by socket path with tab-based routing and tabs_context_mcp result aggregation across browsers.
Browser Tool Definitions & Routing
packages/@ant/claude-for-chrome-mcp/src/browserTools.ts, packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts
Exports BROWSER_TOOLS array (17 tool schemas: navigation, JavaScript, forms, screenshots, GIF recording, tab context, debugging, shortcuts, browser switching, etc.); implements handleToolCall() for routing set_permission_mode, switch_browser, and general tool execution with error/auth/format normalization.
MCP Server Entrypoint
packages/@ant/claude-for-chrome-mcp/src/mcpServer.ts
Exports createClaudeForChromeMcpServer() and createChromeSocketClient() factories; instantiates MCP Server with ListToolsRequest and CallToolRequest handlers, conditionally filtering switch_browser tool when bridge is disabled, and forwarding socket notifications to MCP server.
Module Exports
packages/@ant/claude-for-chrome-mcp/src/index.ts
Refactors from inline stubs to re-exports of BridgeClient, SocketClient implementations, BROWSER_TOOLS, createClaudeForChromeMcpServer, and type definitions from dedicated modules.
Documentation
DEV-LOG.md, docs/features/claude-in-chrome-mcp.md
Adds DEV-LOG entry describing Chrome MCP integration status; adds comprehensive feature documentation covering architecture (Bridge WebSocket / local Socket), call flow (CLI → setup → mcpServer → createClaudeForChromeMcpServer), current blockers, test checklist, file manifest, runtime dependencies, and comparison with /voice and /schedule recovery.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as Claude Code CLI
    participant Server as MCP Server
    participant Client as Socket Client<br/>(Bridge/Pool/Direct)
    participant Ext as Chrome Extension<br/>or Server

    CLI->>Server: startup / ensureConnected()
    Server->>Client: ensureConnected()
    Client->>Ext: connect (WebSocket / Socket)
    Ext-->>Client: connected / authenticated
    Client-->>Server: connection ready

    CLI->>Server: /chrome command<br/>(tool request)
    Server->>Server: ListToolsRequest
    Server-->>CLI: return BROWSER_TOOLS
    
    CLI->>Server: CallToolRequest<br/>(e.g., read_page)
    Server->>Server: handleToolCall()
    Server->>Client: ensureConnected()
    Client-->>Server: connected ✓
    Server->>Client: callTool(name, args)
    
    alt Bridge Mode
        Client->>Ext: tool_call message<br/>(JSON)
        Ext->>Ext: execute tool
        Ext-->>Client: tool_result message
        Client-->>Server: normalized response
    else Socket Mode
        Client->>Ext: execute_tool request<br/>(framed)
        Ext->>Ext: execute tool
        Ext-->>Client: tool response<br/>(framed)
        Client-->>Server: parsed response
    end

    Server->>Server: normalize & convert<br/>(images, errors, auth)
    Server-->>CLI: CallToolResult<br/>(content blocks)
Loading
sequenceDiagram
    participant Client as BridgeClient
    participant WS as Bridge<br/>WebSocket
    participant Ext1 as Extension A<br/>(deviceId:X)
    participant Ext2 as Extension B<br/>(deviceId:Y)

    Client->>Client: first callTool() → no extensions known
    Client->>WS: list_extensions request
    WS->>Ext1: discover
    WS->>Ext2: discover
    Ext1-->>WS: peer_connected
    Ext2-->>WS: peer_connected
    WS-->>Client: [ext_a, ext_b]
    Client->>Client: no persisted match → broadcast pairing_request

    Ext1->>Client: pairing_response (ext_a)
    Client->>Client: select ext_a, persist deviceId:X

    Client->>Ext1: tool_call (target: deviceId:X)
    Ext1->>Ext1: execute browser action
    Ext1-->>Client: tool_result
    Client-->>Client: resolve pending call

    Client->>Client: switchBrowser() requested
    Client->>WS: pairing_request (clear prior)
    Ext2->>Client: pairing_response (ext_b)
    Client->>Client: switch to deviceId:Y
    Client-->>Client: return { deviceId:Y, name:... }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Poem

🐰 Hop hop, the Chrome path is clear!
Socket pools and bridges now appear,
Seventeen tools in toolbox bright,
BridgeClient hops through WebSocket night,
Extension pairing, permission flows,
A rabbit's work that truly glows! 🌟

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: enable Claude in Chrome MCP with full browser control' accurately captures the main change: replacing a non-functional stub with a complete MCP implementation for browser control through the Chrome extension.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@amDosion
Copy link
Copy Markdown
Contributor Author

amDosion commented Apr 3, 2026

image

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (4)
packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts (1)

85-98: Consider adding runtime validation for response structure.

The type assertion at line 85-88 assumes the response follows the expected { result?: ...; error?: ... } shape. While the protocol is well-defined, defensive validation would improve robustness against malformed extension responses.

const response = await socketClient.callTool(name, args, permissionOverrides);

// Current: Type assertion
const { result, error } = response as { result?: ...; error?: ... };

// Safer: Runtime check
if (typeof response !== 'object' || response === null) {
  return { content: [{ type: "text", text: "Tool execution completed" }] };
}
const { result, error } = response as { result?: ...; error?: ... };

This is a minor defensive coding suggestion; the current implementation will work correctly with conforming extensions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/toolCalls.ts around lines 85 - 98,
Add defensive runtime validation for the tool call response before asserting its
shape: after the socketClient.callTool(...) call, verify response is a non-null
object and that at least one of response.result or response.error exists and is
an object or string; if validation fails, return the existing fallback ({
content: [{ type: "text", text: "Tool execution completed" }] }). Then safely
destructure into result and error (used by contentData and isError) knowing the
shape is valid. Ensure validations reference the existing symbols response,
result, error, contentData, and isError so the fallback behavior stays identical
when the response is malformed.
docs/features/claude-in-chrome-mcp.md (1)

16-51: Add language specifiers to fenced code blocks.

Static analysis flagged these code blocks as missing language specifiers. For the ASCII flow diagram (line 16) and directory structure (line 132), use text or plaintext as the language identifier.

📝 Suggested fix

Line 16:

-```
+```text
 CLI 启动

Line 132:

-```
+```text
 packages/@ant/claude-for-chrome-mcp/src/

Also applies to: 132-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/features/claude-in-chrome-mcp.md` around lines 16 - 51, Add a language
specifier (e.g., text or plaintext) to the fenced code blocks that contain the
ASCII flow diagram and the directory listing shown in the diff (the block
starting with "CLI 启动" and the block showing
"packages/@ant/claude-for-chrome-mcp/src/"); update those triple-backtick fences
to ```text so static analysis no longer flags them as missing language
identifiers.
packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts (1)

249-285: Polling loop continues even if connect() throws.

In ensureConnected(), if connect() at line 257 throws an error (e.g., security validation failure), the polling loop at lines 275-283 will continue checking this.connected indefinitely until the 5-second timeout. The error from connect() is not propagated.

This is acceptable behavior since:

  1. Security failures are logged in connect()
  2. The timeout will eventually resolve the promise
  3. The caller will get false from the rejection, indicating connection failed

However, for faster failure feedback on security errors, you could track the validation failure state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts around lines 249
- 285, ensureConnected() starts polling after calling connect(), but if
connect() throws (e.g., security validation failure) the polling loop keeps
running until the timeout and the original error is lost; change
ensureConnected() to catch errors from this.connect() and record the failure
(e.g., set a new property like this.lastConnectError or this.connectError), then
have the polling routine (in ensureConnected) check that property and
immediately reject with that stored error (and clear timers) instead of waiting
for the 5s timeout; reference ensureConnected, connect, this.connected,
this.connecting and add a short-lived error flag/property to propagate connect()
failures quickly.
packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts (1)

8-17: Use the configured src/ alias for these imports.

These new relative imports break the repository import rule for TypeScript files. Please switch them to the configured src/ path alias before merging.

As per coding guidelines, "Import src/ path alias via tsconfig mapping instead of relative paths in imports".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 8 - 17,
Replace the relative imports that pull in SocketConnectionError and the type
symbols from "./mcpSocketClient.js" and "./types.js" with the configured src/
path alias imports (e.g. import { SocketConnectionError } from
"src/mcpSocketClient" and import { localPlatformLabel, type
BridgePermissionRequest, type ChromeExtensionInfo, type ClaudeForChromeContext,
type PermissionMode, type PermissionOverrides, type SocketClient } from
"src/types"), preserving the exact imported identifiers; update any file
extension usage to match the project's tsconfig path mapping so TypeScript
resolves the aliased modules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts:
- Around line 567-574: The log and telemetry currently include the full
user-specific WebSocket URL (wsUrl) and potentially raw bridge messages; update
the code around wsUrl construction and usage (references: wsUrl,
bridgeConfig.url, serverName, logger.info, trackEvent) to redact sensitive
components before logging or tracking by replacing or removing the userId and
any query/path fragments, and ensure trackEvent only sends a minimal allowlist
of safe fields (e.g., host/origin and connection status) rather than the full
URL or raw message payloads; apply the same redaction when logging in the
connection/handshake code paths (also at the other affected site around the code
referenced at lines 617-619).
- Around line 529-564: Awaiting bridgeConfig.getUserId() and
bridgeConfig.getOAuthToken() is unprotected so if either rejects connect()
leaves this.connecting true; wrap each await (or the whole credential-fetch
block in connect()) in a try/catch, and in the catch set this.connecting =
false, compute durationMs using this.connectionStartTime, logger.error with the
caught error, call trackEvent with an appropriate error_type (e.g.,
"credential_lookup_error"), invoke this.context.onAuthenticationError?.(), and
then return/exit so reconnect attempts and ensureConnected() aren’t blocked.
- Around line 626-660: The close/error handlers for this.ws (inside the
ws.on("close", ...) and ws.on("error", ...) blocks) currently schedule
reconnection but leave this.pendingCalls unresolved, causing callers to hang;
add logic to immediately fail and clear all in-flight calls when the socket
drops by iterating this.pendingCalls (or similar pending call store), invoking
each stored reject/callback with a descriptive Error (e.g. "bridge disconnected"
or include code/message), removing entries from the map, and then proceed to set
connected/authenticated flags and call this.scheduleReconnect(); alternatively
extract this into a helper like failPendingCalls(reason) and call it from both
the close and error handlers.
- Around line 452-471: Before calling queryBridgeExtensions(), first check the
WebSocket connection state (this.ws and this.ws.readyState === WebSocket.OPEN)
and return null immediately if the socket is not open; move the existing
readiness check (this.ws?.readyState !== WebSocket.OPEN) to precede the
queryBridgeExtensions() call in the method that uses
selectedDeviceId/previousSelectedDeviceId/pendingPairingRequestId so you don't
wait DISCOVERY_TIMEOUT_MS and mistakenly return "no_other_browsers".

In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts:
- Around line 144-149: The logger calls in the isToolResponse branch and the
final else currently interpolate the message object into a template literal
(producing “[object Object]”); change those logger.info calls to either pass the
object as a separate argument (e.g. logger.info(`[${serverName}] Received tool
response:`, message) and logger.info(`[${serverName}] Received unknown
message:`, message)) or stringify the object (e.g. JSON.stringify(message)) so
the actual message content is logged; update the two spots around
isToolResponse, logger.info and the final else to use one of these approaches
and leave handleResponse(message) unchanged.

---

Nitpick comments:
In `@docs/features/claude-in-chrome-mcp.md`:
- Around line 16-51: Add a language specifier (e.g., text or plaintext) to the
fenced code blocks that contain the ASCII flow diagram and the directory listing
shown in the diff (the block starting with "CLI 启动" and the block showing
"packages/@ant/claude-for-chrome-mcp/src/"); update those triple-backtick fences
to ```text so static analysis no longer flags them as missing language
identifiers.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts:
- Around line 8-17: Replace the relative imports that pull in
SocketConnectionError and the type symbols from "./mcpSocketClient.js" and
"./types.js" with the configured src/ path alias imports (e.g. import {
SocketConnectionError } from "src/mcpSocketClient" and import {
localPlatformLabel, type BridgePermissionRequest, type ChromeExtensionInfo, type
ClaudeForChromeContext, type PermissionMode, type PermissionOverrides, type
SocketClient } from "src/types"), preserving the exact imported identifiers;
update any file extension usage to match the project's tsconfig path mapping so
TypeScript resolves the aliased modules.

In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts:
- Around line 249-285: ensureConnected() starts polling after calling connect(),
but if connect() throws (e.g., security validation failure) the polling loop
keeps running until the timeout and the original error is lost; change
ensureConnected() to catch errors from this.connect() and record the failure
(e.g., set a new property like this.lastConnectError or this.connectError), then
have the polling routine (in ensureConnected) check that property and
immediately reject with that stored error (and clear timers) instead of waiting
for the 5s timeout; reference ensureConnected, connect, this.connected,
this.connecting and add a short-lived error flag/property to propagate connect()
failures quickly.

In `@packages/`@ant/claude-for-chrome-mcp/src/toolCalls.ts:
- Around line 85-98: Add defensive runtime validation for the tool call response
before asserting its shape: after the socketClient.callTool(...) call, verify
response is a non-null object and that at least one of response.result or
response.error exists and is an object or string; if validation fails, return
the existing fallback ({ content: [{ type: "text", text: "Tool execution
completed" }] }). Then safely destructure into result and error (used by
contentData and isError) knowing the shape is valid. Ensure validations
reference the existing symbols response, result, error, contentData, and isError
so the fallback behavior stays identical when the response is malformed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 653a9c53-3dd2-4285-b67f-b4b72e5ee86c

📥 Commits

Reviewing files that changed from the base of the PR and between 29db9d9 and 6738a76.

📒 Files selected for processing (10)
  • DEV-LOG.md
  • docs/features/claude-in-chrome-mcp.md
  • packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts
  • packages/@ant/claude-for-chrome-mcp/src/browserTools.ts
  • packages/@ant/claude-for-chrome-mcp/src/index.ts
  • packages/@ant/claude-for-chrome-mcp/src/mcpServer.ts
  • packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts
  • packages/@ant/claude-for-chrome-mcp/src/mcpSocketPool.ts
  • packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts
  • packages/@ant/claude-for-chrome-mcp/src/types.ts

Comment on lines +452 to +471
const extensions = await this.queryBridgeExtensions();
const currentDeviceId =
this.selectedDeviceId ?? this.previousSelectedDeviceId;
if (
extensions.length === 0 ||
(extensions.length === 1 &&
(!currentDeviceId || extensions[0]!.deviceId === currentDeviceId))
) {
return "no_other_browsers";
}

this.previousSelectedDeviceId = this.selectedDeviceId;
this.selectedDeviceId = undefined;
this.discoveryComplete = false;
this.pairingInProgress = false;

const requestId = crypto.randomUUID();
this.pendingPairingRequestId = requestId;
if (this.ws?.readyState !== WebSocket.OPEN) {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Check the socket state before listing extensions.

When the bridge is already disconnected, queryBridgeExtensions() waits for DISCOVERY_TIMEOUT_MS and returns [], so this path reports "no_other_browsers" after ~5 seconds instead of the disconnected null case.

Suggested fix
  public async switchBrowser(): Promise<
    | {
        deviceId: string;
        name: string;
      }
    | "no_other_browsers"
    | null
  > {
+    if (this.ws?.readyState !== WebSocket.OPEN) {
+      return null;
+    }
+
    const extensions = await this.queryBridgeExtensions();
    const currentDeviceId =
      this.selectedDeviceId ?? this.previousSelectedDeviceId;
    if (
      extensions.length === 0 ||
      (extensions.length === 1 &&
        (!currentDeviceId || extensions[0]!.deviceId === currentDeviceId))
    ) {
      return "no_other_browsers";
    }
@@
-    if (this.ws?.readyState !== WebSocket.OPEN) {
-      return null;
-    }
     this.ws.send(
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const extensions = await this.queryBridgeExtensions();
const currentDeviceId =
this.selectedDeviceId ?? this.previousSelectedDeviceId;
if (
extensions.length === 0 ||
(extensions.length === 1 &&
(!currentDeviceId || extensions[0]!.deviceId === currentDeviceId))
) {
return "no_other_browsers";
}
this.previousSelectedDeviceId = this.selectedDeviceId;
this.selectedDeviceId = undefined;
this.discoveryComplete = false;
this.pairingInProgress = false;
const requestId = crypto.randomUUID();
this.pendingPairingRequestId = requestId;
if (this.ws?.readyState !== WebSocket.OPEN) {
return null;
if (this.ws?.readyState !== WebSocket.OPEN) {
return null;
}
const extensions = await this.queryBridgeExtensions();
const currentDeviceId =
this.selectedDeviceId ?? this.previousSelectedDeviceId;
if (
extensions.length === 0 ||
(extensions.length === 1 &&
(!currentDeviceId || extensions[0]!.deviceId === currentDeviceId))
) {
return "no_other_browsers";
}
this.previousSelectedDeviceId = this.selectedDeviceId;
this.selectedDeviceId = undefined;
this.discoveryComplete = false;
this.pairingInProgress = false;
const requestId = crypto.randomUUID();
this.pendingPairingRequestId = requestId;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 452 -
471, Before calling queryBridgeExtensions(), first check the WebSocket
connection state (this.ws and this.ws.readyState === WebSocket.OPEN) and return
null immediately if the socket is not open; move the existing readiness check
(this.ws?.readyState !== WebSocket.OPEN) to precede the queryBridgeExtensions()
call in the method that uses
selectedDeviceId/previousSelectedDeviceId/pendingPairingRequestId so you don't
wait DISCOVERY_TIMEOUT_MS and mistakenly return "no_other_browsers".

Comment on lines +529 to +564
logger.debug(`[${serverName}] Fetching user ID for bridge connection`);
const fetchedUserId = await bridgeConfig.getUserId();
if (!fetchedUserId) {
const durationMs = Date.now() - this.connectionStartTime;
logger.error(
`[${serverName}] No user ID available after ${durationMs}ms`,
);
trackEvent?.("chrome_bridge_connection_failed", {
duration_ms: durationMs,
error_type: "no_user_id",
reconnect_attempt: this.reconnectAttempts,
});
this.connecting = false;
this.context.onAuthenticationError?.();
return;
}
userId = fetchedUserId;

logger.debug(
`[${serverName}] Fetching OAuth token for bridge connection`,
);
token = await bridgeConfig.getOAuthToken();
if (!token) {
const durationMs = Date.now() - this.connectionStartTime;
logger.error(
`[${serverName}] No OAuth token available after ${durationMs}ms`,
);
trackEvent?.("chrome_bridge_connection_failed", {
duration_ms: durationMs,
error_type: "no_oauth_token",
reconnect_attempt: this.reconnectAttempts,
});
this.connecting = false;
this.context.onAuthenticationError?.();
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clear this.connecting if credential lookup throws.

bridgeConfig.getUserId() and bridgeConfig.getOAuthToken() are awaited outside any try/catch. If either rejects, connect() exits with this.connecting still true, which blocks later reconnect attempts and makes ensureConnected() poll until its timeout.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 529 -
564, Awaiting bridgeConfig.getUserId() and bridgeConfig.getOAuthToken() is
unprotected so if either rejects connect() leaves this.connecting true; wrap
each await (or the whole credential-fetch block in connect()) in a try/catch,
and in the catch set this.connecting = false, compute durationMs using
this.connectionStartTime, logger.error with the caught error, call trackEvent
with an appropriate error_type (e.g., "credential_lookup_error"), invoke
this.context.onAuthenticationError?.(), and then return/exit so reconnect
attempts and ensureConnected() aren’t blocked.

Comment on lines +567 to +574
// Connect to user-specific endpoint: /chrome/<user_id>
const wsUrl = `${bridgeConfig.url}/chrome/${userId}`;
logger.info(`[${serverName}] Connecting to bridge: ${wsUrl}`);

// Track connection started
trackEvent?.("chrome_bridge_connection_started", {
bridge_url: wsUrl,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Redact sensitive bridge data from logs and telemetry.

wsUrl includes the user-specific /chrome/${userId} path, and raw bridge messages can contain URLs, tab metadata, DOM text, and tool results. Both of these paths persist sensitive browsing/account data that should be redacted down to a small allowlist of safe fields.

Also applies to: 617-619

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 567 -
574, The log and telemetry currently include the full user-specific WebSocket
URL (wsUrl) and potentially raw bridge messages; update the code around wsUrl
construction and usage (references: wsUrl, bridgeConfig.url, serverName,
logger.info, trackEvent) to redact sensitive components before logging or
tracking by replacing or removing the userId and any query/path fragments, and
ensure trackEvent only sends a minimal allowlist of safe fields (e.g.,
host/origin and connection status) rather than the full URL or raw message
payloads; apply the same redaction when logging in the connection/handshake code
paths (also at the other affected site around the code referenced at lines
617-619).

Comment on lines +626 to +660
this.ws.on("close", (code: number) => {
const durationSinceConnect = this.connectionEstablishedTime
? Date.now() - this.connectionEstablishedTime
: 0;
logger.info(
`[${serverName}] Bridge connection closed (code: ${code}, duration: ${durationSinceConnect}ms)`,
);
trackEvent?.("chrome_bridge_disconnected", {
close_code: code,
duration_since_connect_ms: durationSinceConnect,
reconnect_attempt: this.reconnectAttempts + 1,
});
this.connected = false;
this.authenticated = false;
this.connecting = false;
this.connectionEstablishedTime = null;
this.scheduleReconnect();
});

this.ws.on("error", (error: Error) => {
const durationMs = this.connectionStartTime
? Date.now() - this.connectionStartTime
: 0;
logger.error(
`[${serverName}] Bridge WebSocket error after ${durationMs}ms: ${error.message}`,
);
trackEvent?.("chrome_bridge_connection_failed", {
duration_ms: durationMs,
error_type: "websocket_error",
reconnect_attempt: this.reconnectAttempts,
});
this.connected = false;
this.authenticated = false;
this.connecting = false;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fail in-flight work immediately when the socket drops.

The close/error handlers schedule reconnects, but they leave pendingCalls alive until each tool timeout expires. A mid-call disconnect can therefore stall the caller for up to 120 seconds even though that bridge session is already gone.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 626 -
660, The close/error handlers for this.ws (inside the ws.on("close", ...) and
ws.on("error", ...) blocks) currently schedule reconnection but leave
this.pendingCalls unresolved, causing callers to hang; add logic to immediately
fail and clear all in-flight calls when the socket drops by iterating
this.pendingCalls (or similar pending call store), invoking each stored
reject/callback with a descriptive Error (e.g. "bridge disconnected" or include
code/message), removing entries from the map, and then proceed to set
connected/authenticated flags and call this.scheduleReconnect(); alternatively
extract this into a helper like failPendingCalls(reason) and call it from both
the close and error handlers.

Comment on lines +144 to +149
} else if (isToolResponse(message)) {
logger.info(`[${serverName}] Received tool response: ${message}`);
this.handleResponse(message);
} else {
logger.info(`[${serverName}] Received unknown message: ${message}`);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix object logging to show actual content.

Lines 145 and 148 log the message object directly using template literal interpolation, which will output [object Object] instead of the actual content.

🔧 Suggested fix
           } else if (isToolResponse(message)) {
-            logger.info(`[${serverName}] Received tool response: ${message}`);
+            logger.info(`[${serverName}] Received tool response: ${JSON.stringify(message)}`);
             this.handleResponse(message);
           } else {
-            logger.info(`[${serverName}] Received unknown message: ${message}`);
+            logger.info(`[${serverName}] Received unknown message: ${JSON.stringify(message)}`);
           }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts around lines 144
- 149, The logger calls in the isToolResponse branch and the final else
currently interpolate the message object into a template literal (producing
“[object Object]”); change those logger.info calls to either pass the object as
a separate argument (e.g. logger.info(`[${serverName}] Received tool response:`,
message) and logger.info(`[${serverName}] Received unknown message:`, message))
or stringify the object (e.g. JSON.stringify(message)) so the actual message
content is logged; update the two spots around isToolResponse, logger.info and
the final else to use one of these approaches and leave handleResponse(message)
unchanged.

Copy link
Copy Markdown

@chaoswork chaoswork left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@claude-code-best
Copy link
Copy Markdown
Owner

@amDosion 你是有 claude 订阅吗, 我这边显示要有订阅才行

@amDosion
Copy link
Copy Markdown
Contributor Author

amDosion commented Apr 3, 2026 via email

@amDosion
Copy link
Copy Markdown
Contributor Author

amDosion commented Apr 3, 2026

我的截图左上角有个标识的,需要在浏览器安装claude 的官方插件:https://chromewebstore.google.com/detail/claude/fcoeoabgfenejglbffodgkkbkcdhcgfn?pli=1

@amDosion
Copy link
Copy Markdown
Contributor Author

amDosion commented Apr 3, 2026

还有一个就是接入chrome-dev-mcp,安装这个mcp也能操作浏览器

@claude-code-best claude-code-best merged commit 8cef1b6 into claude-code-best:main Apr 3, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants